//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Text;
using EffiProz.Core.RowIO;
using EffiProz.Core.Lib;
using EffiProz.Core.Results;
using EffiProz.Core.Errors;
using EffiProz.Core.DataTypes;


namespace EffiProz.Core.ScriptIO
{

    /**
     * Handles all scripting and logging operations. A script consists of two blocks:<p>
     *
     * DDL: SQL statements for table and user definitions
     * DATA: INSERT statements for memory tables
     *
     * This happens as part of the CHECKPOINT and SHUTDOWN COMPACT
     * process. In this case, the
     * DATA block contains the CACHED table data as well.<p>
     *
     * A related use for this class is for saving a current snapshot of the
     * database data to a user-defined file with the SCRIPT command
     *
     * A log consists of SQL statements of different types. Each statement is
     * encoded as ASCII and saved.
     *
     *
     * @author fredt@users
     * @version 1.8.0
     * @since 1.7.2
     */
    public class ScriptWriterText : ScriptWriterBase
    {
        protected RowOutputTextLog rowOut;

        /** @todo - perhaps move this global into a lib utility class */
        public static byte[] BYTES_LINE_SEP;
        static byte[] BYTES_COMMIT;
        static byte[] BYTES_INSERT_INTO;
        static byte[] BYTES_VALUES;
        static byte[] BYTES_TERM;
        static byte[] BYTES_DELETE_FROM;
        static byte[] BYTES_WHERE;
        static byte[] BYTES_SEQUENCE;
        static byte[] BYTES_SEQUENCE_MID;
        static byte[] BYTES_C_ID_INIT;
        static byte[] BYTES_C_ID_TERM;
        static byte[] BYTES_SCHEMA;

        static ScriptWriterText()
        {
            String sLineSep = Environment.NewLine;

            try
            {
                BYTES_LINE_SEP = Encoding.UTF8.GetBytes(Environment.NewLine);
                BYTES_COMMIT = Encoding.UTF8.GetBytes("COMMIT");
                BYTES_INSERT_INTO = Encoding.UTF8.GetBytes("INSERT INTO ");
                BYTES_VALUES = Encoding.UTF8.GetBytes(" VALUES(");
                BYTES_TERM = Encoding.UTF8.GetBytes(")");
                BYTES_DELETE_FROM = Encoding.UTF8.GetBytes("DELETE FROM ");
                BYTES_WHERE = Encoding.UTF8.GetBytes(" WHERE ");
                BYTES_SEQUENCE = Encoding.UTF8.GetBytes("ALTER SEQUENCE ");
                BYTES_SEQUENCE_MID = Encoding.UTF8.GetBytes(" RESTART WITH ");
                BYTES_C_ID_INIT = Encoding.UTF8.GetBytes("/*C");
                BYTES_C_ID_TERM = Encoding.UTF8.GetBytes("*/");
                BYTES_SCHEMA = Encoding.UTF8.GetBytes("SET SCHEMA ");
            }
            catch (Exception )
            {
                Error.runtimeError(ErrorCode.U_S0500, "ScriptWriterText");
            }
        }

        public ScriptWriterText(Database db, String file,
                                bool includeCachedData, bool newFile,
                                bool isDump)
            : base(db, file, includeCachedData, newFile, isDump)
        {

        }

        protected override void initBuffers()
        {
            rowOut = new RowOutputTextLog();
        }

        protected override void writeDataTerm() { }

        protected override void addSessionId(Session session)
        {

            if (session == null)
            {
                return;
            }

            if (session != currentSession)
            {
                rowOut.write(BYTES_C_ID_INIT);
                rowOut.writeLong(session.getId());
                rowOut.write(BYTES_C_ID_TERM);

                currentSession = session;
            }

            if (schemaToLog != session.loggedSchema)
            {
                writeSchemaStatement(schemaToLog);

                session.loggedSchema = schemaToLog;
            }
        }

        private void writeSchemaStatement(QNameManager.QName schema)
        {

            rowOut.write(BYTES_SCHEMA);
            rowOut.writeString(schema.statementName);
            rowOut.write(BYTES_LINE_SEP);
        }

        public override void writeLogStatement(Session session,
                                      String s)
        {

            schemaToLog = session.currentSchema;
            busyWriting = true;

            rowOut.reset();
            addSessionId(session);
            rowOut.writeString(s);
            rowOut.write(BYTES_LINE_SEP);
            writeRowOutToFile();

            byteCount += rowOut.size();
            needsSync = true;
            busyWriting = false;

            if (forceSync)
            {
                sync();
            }
        }

        public override void writeRow(Session session, Table table,
                                Object[] data)
        {

            schemaToLog = table.getName().schema;
            busyWriting = true;

            rowOut.reset();
            ((RowOutputTextLog)rowOut).setMode(RowOutputTextLog.MODE_INSERT);
            addSessionId(session);
            rowOut.write(BYTES_INSERT_INTO);
            rowOut.writeString(table.getName().statementName);
            rowOut.write(BYTES_VALUES);
            rowOut.writeData(data, table.getColumnTypes());
            rowOut.write(BYTES_TERM);
            rowOut.write(BYTES_LINE_SEP);
            writeRowOutToFile();

            byteCount += rowOut.size();
            busyWriting = false;

            if (forceSync)
            {
                sync();
            }
        }

        protected override void writeTableInit(Table t)
        {

            if (t.isEmpty(currentSession))
            {
                return;
            }

            if (schemaToLog == currentSession.loggedSchema)
            {
                return;
            }

            rowOut.reset();
            writeSchemaStatement(t.getName().schema);
            writeRowOutToFile();

            currentSession.loggedSchema = schemaToLog;
        }

        public override void writeInsertStatement(Session session, Table table,
                                         Object[] data)
        {

            schemaToLog = table.getName().schema;

            writeRow(session, table, data);
        }

        public override void writeDeleteStatement(Session session, Table table,
                                         Object[] data)
        {

            schemaToLog = table.getName().schema;
            busyWriting = true;

            rowOut.reset();
            ((RowOutputTextLog)rowOut).setMode(RowOutputTextLog.MODE_DELETE);
            addSessionId(session);
            rowOut.write(BYTES_DELETE_FROM);
            rowOut.writeString(table.getName().statementName);
            rowOut.write(BYTES_WHERE);
            rowOut.writeData(table.getColumnCount(), table.getColumnTypes(), data,
                             table.columnList, table.getPrimaryKey());
            rowOut.write(BYTES_LINE_SEP);
            writeRowOutToFile();

            byteCount += rowOut.size();
            busyWriting = false;

            if (forceSync)
            {
                sync();
            }
        }

        public override void writeSequenceStatement(Session session,
                                           NumberSequence seq)
        {

            schemaToLog = seq.getName().schema;
            busyWriting = true;

            rowOut.reset();
            addSessionId(session);
            rowOut.write(BYTES_SEQUENCE);
            rowOut.writeString(seq.getSchemaName().statementName);
            rowOut.write('.');
            rowOut.writeString(seq.getName().statementName);
            rowOut.write(BYTES_SEQUENCE_MID);
            rowOut.writeLong(seq.peek());
            rowOut.write(BYTES_LINE_SEP);
            writeRowOutToFile();

            byteCount += rowOut.size();
            needsSync = true;
            busyWriting = false;

            if (forceSync)
            {
                sync();
            }
        }

        public override void writeCommitStatement(Session session)
        {

            busyWriting = true;

            rowOut.reset();
            addSessionId(session);
            rowOut.write(BYTES_COMMIT);
            rowOut.write(BYTES_LINE_SEP);
            writeRowOutToFile();

            byteCount += rowOut.size();
            needsSync = true;
            busyWriting = false;

            if (forceSync || writeDelay == 0)
            {
                sync();
            }
        }

        //protected override void Finalize()
        //{
        //    sync();
        //}

        public virtual void writeRowOutToFile()
        {

            lock (fileStreamOut)
            {
                fileStreamOut.Write(rowOut.getBuffer(), 0, rowOut.size());
            }
        }

    }
}


